package org.zoeframework.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Zoe.Zhang
 * @date 2022/01/16
 * @description
 */
@Slf4j
public class ClassUtil {
    public static final String FILE_PROTOCOL = "file";

    /**
     * 获取包下面所有的类集合
     *
     * @param packageName
     * @return
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        // 1:获取到类的加载器
        ClassLoader classLoader = getClassLoader();
        // 2:通过类加载器获取到加载的资源
        String replacePackageName = StrUtil.replace(packageName, ".", "/");
        URL url = classLoader.getResource(replacePackageName);
        if (ObjectUtil.isNull(url)) {
            log.warn("没有解析到资源===>{}", packageName);
            return null;
        }
        // 3:依据不同的资源类型,采用不同的方式获取资源的集合

        Set<Class<?>> clazzSet = null;
        if (StrUtil.equalsIgnoreCase(url.getProtocol(), FILE_PROTOCOL)) {
            clazzSet = new HashSet<>();
            File packageDirectory = FileUtil.file(url.getPath());
            extractClassFile(clazzSet, packageDirectory, packageName);
        }
        return clazzSet;
    }


    /**
     * 获取ClassLoader
     *
     * @return
     */
    private static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 递归获取目标package里面所有的class文件(包括子package里面的class文件)
     *
     * @param emptyClazzSet 装载目标类的集合
     * @param fileSource    文件或者目录
     * @param packageName   包名
     */
    private static void extractClassFile(Set<Class<?>> emptyClazzSet, File fileSource, String packageName) {
        // 如果不是文件夹直接return
        if (!fileSource.isDirectory()) {
            return;
        }
        // 返回fileSource下面所有的文件夹
        File[] listFiles = fileSource.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                // 如果是文件夹则返回,如果是class文件,则添加到emptyClassSet里面
                if (file.isDirectory()) {
                    return true;
                }
                else {
                    // 获取文件的绝对路径   路径+文件名
                    String absolutePath = file.getAbsolutePath();
                    if (StrUtil.endWith(absolutePath, ".class")) {
                        // 如果是class文件则直接加载,对应的类实例
                        addToClazzSet(absolutePath);
                    }
                }
                return false;
            }

            private void addToClazzSet(String absolutePath) {
                // 1:从class文件的绝对值路径里面踢出去包含了package的类型
                // 如  D:\06.IDEAWK\zoe-framework\target\classes\com\zoe\spring\entity\dto\MainPageInfoDTO.class
                // 需要弄成 com.zoe.spring.entity.dto.MainPageInfoDTO
                absolutePath = StrUtil.replace(absolutePath, File.separator, "."); // D:.06.IDEAWK.zoe-framework.target.classes.com.zoe.spring.entity.dto.MainPageInfoDTO.class
                int startIndex = absolutePath.indexOf(packageName);
                int endIndex = absolutePath.lastIndexOf(".");
                String className = StrUtil.sub(absolutePath, startIndex, endIndex);
                // 2:通过反射机制获取对应的Class对象并加入到emptyClassSet里面
                Class targetClass = loadClass(className);
                emptyClazzSet.add(targetClass);
            }
        });


        if (ArrayUtil.isNotEmpty(listFiles)) {
            for (File file : listFiles) {
                extractClassFile(emptyClazzSet, file, packageName);
            }
        }
    }

    /**
     * 获取Class对象
     *
     * @param className class全面 = 包名+类名
     */
    private static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("类加载异常===>{}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 实例化Clazz
     *
     * @param clazz      Class对象
     * @param accessible 访问修饰符
     * @param <T>
     * @return 实例化对象
     */
    public static <T> T newInstance(Class<?> clazz, boolean accessible) {
        try {
            Constructor<?> constructor = clazz.getConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("getInstance error===>{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * @param field      成员变量
     * @param targetBean 需要注入类的实例
     * @param value      成员变量的实例
     * @param accessible 是否允许设置私有化属性
     */
    public static void setField(Field field, Object targetBean, Object value, boolean accessible) {
        field.setAccessible(accessible);
        try {
            field.set(targetBean, value);
        } catch (IllegalAccessException e) {
            log.error("setField error");
            throw new RuntimeException(e);
        }
    }
}
